/*
 * MIT License
 * Copyright (c) 2021 _VIFEXTech
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#include "PM_Log.h"
#include "PageManager.h"
#include <stdlib.h>

#define CONSTRAIN(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))

/* The distance threshold to trigger the drag */
#define PM_INDEV_DEF_DRAG_THROW 20

/**
 * @brief  Page drag event callback
 * @param  event: Pointer to event structure
 * @retval None
 */
void PageManager::onRootDragEvent(lv_event_t* event)
{
	lv_event_code_t eventCode = lv_event_get_code(event);

	if (!(eventCode == LV_EVENT_PRESSED || eventCode == LV_EVENT_PRESSING || eventCode == LV_EVENT_RELEASED)) {
		return;
	}

	lv_obj_t* root = lv_event_get_current_target(event);
	PageBase* base = (PageBase*)lv_event_get_user_data(event);

	if (base == nullptr) {
		PM_LOG_ERROR("Page base is NULL");
		return;
	}

	PageManager* manager = base->_Manager;
	LoadAnimAttr_t animAttr;

	if (!manager->GetCurrentLoadAnimAttr(&animAttr)) {
		PM_LOG_ERROR("Can't get current anim attr");
		return;
	}

	if (eventCode == LV_EVENT_PRESSED) {
		if (manager->_AnimState.IsSwitchReq) {
			return;
		}

		if (!manager->_AnimState.IsBusy) {
			return;
		}

		PM_LOG_INFO("Root anim interrupted");
		lv_anim_del(root, animAttr.setter);
		manager->_AnimState.IsBusy = false;

		/* Temporary showing the bottom page */
		PageBase* bottomPage = manager->GetStackTopAfter();
		lv_obj_clear_flag(bottomPage->_root, LV_OBJ_FLAG_HIDDEN);
	} else if (eventCode == LV_EVENT_PRESSING) {
		lv_coord_t cur = animAttr.getter(root);

		lv_coord_t max = std::max(animAttr.pop.exit.start, animAttr.pop.exit.end);
		lv_coord_t min = std::min(animAttr.pop.exit.start, animAttr.pop.exit.end);

		lv_point_t offset;
		lv_indev_get_vect(lv_indev_get_act(), &offset);

		if (animAttr.dragDir == ROOT_DRAG_DIR_HOR) {
			cur += offset.x;
		} else if (animAttr.dragDir == ROOT_DRAG_DIR_VER) {
			cur += offset.y;
		}

		animAttr.setter(root, CONSTRAIN(cur, min, max));
	} else if (eventCode == LV_EVENT_RELEASED) {
		if (manager->_AnimState.IsSwitchReq) {
			return;
		}

		lv_coord_t offset_sum = animAttr.push.enter.end - animAttr.push.enter.start;

		lv_coord_t x_predict = 0;
		lv_coord_t y_predict = 0;
		RootGetDragPredict(&x_predict, &y_predict);

		lv_coord_t start = animAttr.getter(root);
		lv_coord_t end = start;

		if (animAttr.dragDir == ROOT_DRAG_DIR_HOR) {
			end += x_predict;
			PM_LOG_INFO("Root drag x_predict = %d", end);
		} else if (animAttr.dragDir == ROOT_DRAG_DIR_VER) {
			end += y_predict;
			PM_LOG_INFO("Root drag y_predict = %d", end);
		}

		if (std::abs(end) > std::abs((int)offset_sum) / 2) {
			lv_async_call(onRootAsyncLeave, base);
		} else if (end != animAttr.push.enter.end) {
			manager->_AnimState.IsBusy = true;

			lv_anim_t a;
			manager->AnimDefaultInit(&a);
			lv_anim_set_user_data(&a, manager);
			lv_anim_set_var(&a, root);
			lv_anim_set_values(&a, start, animAttr.push.enter.end);
			lv_anim_set_exec_cb(&a, animAttr.setter);
			lv_anim_set_ready_cb(&a, onRootDragAnimFinish);
			lv_anim_start(&a);
			PM_LOG_INFO("Root drag anim start");
		}
	}
}

/**
 * @brief  Drag animation end event callback
 * @param  a: Pointer to animation
 * @retval None
 */
void PageManager::onRootDragAnimFinish(lv_anim_t* a)
{
	PageManager* manager = (PageManager*)lv_anim_get_user_data(a);
	PM_LOG_INFO("Root drag anim finish");
	manager->_AnimState.IsBusy = false;

	/* Hide the bottom page */
	PageBase* bottomPage = manager->GetStackTopAfter();
	if (bottomPage) {
		lv_obj_add_flag(bottomPage->_root, LV_OBJ_FLAG_HIDDEN);
	}
}

/**
 * @brief  Enable root's drag function
 * @param  root: Pointer to the root object
 * @retval None
 */
void PageManager::RootEnableDrag(lv_obj_t* root)
{
	PageBase* base = (PageBase*)lv_obj_get_user_data(root);
	lv_obj_add_event_cb(
	    root,
	    onRootDragEvent,
	    LV_EVENT_ALL,
	    base);
	PM_LOG_INFO("Page(%s) Root drag enabled", base->_Name);
}

/**
 * @brief  Asynchronous callback when dragging ends
 * @param  data: Pointer to the base class of the page
 * @retval None
 */
void PageManager::onRootAsyncLeave(void* data)
{
	PageBase* base = (PageBase*)data;
	PM_LOG_INFO("Page(%s) send event: LV_EVENT_LEAVE, need to handle...", base->_Name);
	lv_event_send(base->_root, LV_EVENT_LEAVE, base);
}

/**
 * @brief  Get drag inertia prediction stop point
 * @param  x: x stop point
 * @param  y: y stop point
 * @retval None
 */
void PageManager::RootGetDragPredict(lv_coord_t* x, lv_coord_t* y)
{
	lv_indev_t* indev = lv_indev_get_act();
	lv_point_t vect;
	lv_indev_get_vect(indev, &vect);

	lv_coord_t y_predict = 0;
	lv_coord_t x_predict = 0;

	while (vect.y != 0) {
		y_predict += vect.y;
		vect.y = vect.y * (100 - PM_INDEV_DEF_DRAG_THROW) / 100;
	}

	while (vect.x != 0) {
		x_predict += vect.x;
		vect.x = vect.x * (100 - PM_INDEV_DEF_DRAG_THROW) / 100;
	}

	*x = x_predict;
	*y = y_predict;
}
